/*
 * Copyright (C) 2015, apexes.net. All rights reserved.
 * 
 *        http://www.apexes.net
 * 
 */
package net.apexes.wsonrpc.core;

import net.apexes.wsonrpc.core.exception.RemoteException;
import net.apexes.wsonrpc.core.exception.WsonrpcException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * 
 * @author <a href=mailto:hedyn@foxmail.com>HeDYn</a>
 *
 */
public class WsonrpcEndpoint implements WsonrpcRemote {

    private static final Logger LOG = LoggerFactory.getLogger(WsonrpcEndpoint.class);

    private final WsonrpcEngine wsonrpcEngine;
    private WebSocketSession session;
    private String closedSessionId;

    protected WsonrpcEndpoint(WsonrpcEngine wsonrpcEngine) {
        this.wsonrpcEngine = wsonrpcEngine;
    }

    protected WsonrpcEngine getWsonrpcEngine() {
        return wsonrpcEngine;
    }

    public ServiceRegistry getServiceRegistry() {
        return wsonrpcEngine.getServiceRegistry();
    }

    protected synchronized final void openSession(WebSocketSession session) {
        this.session = session;
    }

    protected synchronized void closeSession() throws Exception {
        if (session != null) {
            session.close();
            closedSessionId = session.getId();
            session = null;
        }
    }

    protected WebSocketSession getSession() {
        return session;
    }

    protected String getClosedSessionId() {
        return closedSessionId;
    }
    
    protected void verifyOnline() throws WsonrpcException {
        if (!isConnected()) {
            throw new WsonrpcException("Connection is closed.");
        }
    }
    
    @Override
    public boolean isConnected() {
        return session != null && session.isOpen();
    }

    @Override
    public String getSessionId() {
        if (session != null) {
            return session.getId();
        }
        return null;
    }

    @Override
    public void disconnect() {
        try {
            closeSession();
        } catch (Exception e) {
            LOG.warn("disconnect error!");
        }
    }
    
    @Override
    public void ping(byte[] payload) throws Exception {
        verifyOnline();
        session.ping(payload);
    }

    @Override
    public void notify(String method, Object[] args) throws IOException, WsonrpcException {
        notify(method, args, null);
    }

    @Override
    public void notify(String method, Object[] args, String trace) throws IOException, WsonrpcException {
        verifyOnline();
        wsonrpcEngine.notify(getSession(), method, args, trace);
    }

    @Override
    public <T> T request(String method, Object[] args, Type returnType, int timeout)
            throws IOException, WsonrpcException, RemoteException {
        Future<T> future = request(method, args, returnType);
        try {
            if (timeout <= 0) {
                return future.get();
            } else {
                return future.get(timeout, TimeUnit.MILLISECONDS);
            }
        } catch (Throwable e) {
            Throwable throwable = e;
            if (throwable instanceof ExecutionException) {
                throwable = throwable.getCause();
            }
            if (throwable instanceof RemoteException) {
                throw (RemoteException) throwable;
            }
            if (throwable instanceof WsonrpcException) {
                throw (WsonrpcException) throwable;
            }
            throw new WsonrpcException("Invoke " + method + " failed.", throwable);
        }
    }

    @Override
    public <T> Future<T> request(String method, Object[] args, Type returnType) throws IOException, WsonrpcException {
        verifyOnline();
        return wsonrpcEngine.request(session, method, args, returnType);
    }

    @Override
    public void request(String method, Object[] args, WsonrpcCallback callback) throws IOException, WsonrpcException {
        wsonrpcEngine.request(session, method, args, callback);
    }
    
}
